<?php
declare (strict_types=1);

namespace app\user\logic\user;

use app\admin\logic\system\Config as SystemConfig;
use app\common\event\subscribes\notify\Notify as NotifyEvent;
use app\common\exception\LogicException;
use app\common\logic\{Verify};
use app\common\model\payment\PaymentOrder;
use app\course\logic\coupon\CouponUser;
use app\user\event\subscribes\user\Account as UserSubscribe;
use app\user\model\user\{Account as UserModel, ThirdLogin};
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\{Event};
use think\helper\Str;

/**
 * @method whereId(int|mixed $user_id)
 * @method wherePhone($phone)
 */
class Account extends UserModel
{
    /**
     * 添加用户
     * Author   Martinsun<syh@sunyonghong.com>
     * Date 2020-03-03
     *
     * @param array $data 数据
     * @return Account
     */
    public function addUser(array $data): Account
    {
        // 验证数据
        $this->getValidate()->scene('add')->failException()->check($data);
        if (isset($data['phone'])) {
            // 检测机构下是否已经注册
            $mhmId   = $data['mhm_id'] ?? request()->mhm_id;
            $hasUser = $this->where('mhm_id', $mhmId)->where('phone', $data['phone'])->findOrEmpty();
            if (!$hasUser->isEmpty()) {
                throw new LogicException('此账号已注册');
            }
        }

        $user = $this::create($data);

        if ($user->isEmpty()) throw new LogicException('登录失败', '注册失败');
        // 注册成功
        // 注册积分
        $user->credit_key = 'register';
        $user->rel_pk     = $user->id;
        Event::subscribe(UserSubscribe::class);
        Event::trigger('UserReg', $user);

        return $user;
    }

    /**
     * 注册用户
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-16
     */
    public function register(array $data)
    {
        $data['mhm_id'] = $data['mhm_id'] ?? request()->mhm_id;
        // 验证数据
        $this->getValidate()->scene('reg')->failException()->check($data);
        $exist = $this->where($data)->findOrEmpty();
        if (!$exist->isEmpty()) throw new LogicException('此账号已注册');

        $user = $this::create($data);
        if ($user->isEmpty()) throw new LogicException('注册失败');
        // 新用户活动券
        $new  = app(Ticket::class)->target('1')->field('id')->buildSql();
        $all  = app(Ticket::class)->target('0')->field('id')->buildSql();
        $time = time();
        try {
            $raw    = "(json_extract( `ext_data`, '$.start_time' ) < '" . $time . "' AND ticket_id IN $new) OR (ticket_id IN $all)";
            $ticket = app(TicketCoupon::class)->field('id,coupon_type,maxprice,price,discount,effective_day')->whereRaw($raw)->where('derive_stime', '<', $time)->where('derive_etime', '>', $time)->select()->toArray();
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            throw new LogicException($e->getMessage());
        }
        $user->ticket = $ticket;
        // 注册成功
        // 注册积分
        /* onAfterInsert */
        // $user->credit_key = 'register';
        // $user->rel_pk     = $user->id;
        Event::subscribe(UserSubscribe::class);
        Event::trigger('UserReg', $user);

        // Event::trigger('SCRM', ['user_name' => $user->user_name, 'phone' => $user->phone, 'id' => $user->id]);
        // trace($user, 'info');
        return $user;
    }

    /**
     * 编辑用户
     * Author   Martinsun<syh@sunyonghong.com>
     * Date 2020-03-04
     *
     * @param Account $user
     * @param array   $data 数据
     * @return    Account
     */
    public function editUser(Account $user, array $data): Account
    {
        // 验证数据
        $this->validateUserData($user, $data);

        $user->nick_name = $data['nick_name'] ?? $user->nick_name;
        $user->gender    = $data['gender'] ?? $user->gender;
        $user->signature = $data['signature'] ?? $user->signature;
        $user->avatar    = $data['avatar'] ?? $user->avatar;
        // 检查手机号
        $phone = cache('verify_phone_' . $user->id);
        if ($phone && $phone === $data['phone']) {
            $user->phone = $phone;
        }
        // return $data;
        if (!$user->save()) throw new LogicException('编辑失败');

        $user->hidden($this::unnecessaryField());

        return $user;
    }

    public function validateUserData(Account $user, array $data)
    {
        if (isset($data['user_name']) && $user->user_name != $data['user_name']) {
            if ($user->rest_times) {
                if (!preg_match('/^[\w\x{4e00}-\x{9fa5}]+$/u', $data['user_name'])) throw new LogicException('用户名格式错误');

                $same_user_name = $this->where('user_name', $data['user_name'])
                    ->where('id', '<>', $user->id)
                    ->where('mhm_id', $user->mhm_id ?? request()->mhm_id)
                    ->findOrEmpty();

                if (!$same_user_name->isEmpty()) throw new LogicException('用户名已存在');

                $user->user_name  = $data['user_name'];
                $user->rest_times -= 1;
            }
        }

        if (isset($data['nick_name']) && !preg_match('/^[\w\x{4e00}-\x{9fa5}]+$/u', $data['nick_name'])) throw new LogicException('昵称格式错误');

        if (isset($data['avatar']) && !preg_match('/^\d+$/', (string)$data['avatar'])) throw new LogicException('请上传头像');

        if (isset($data['gender']) && !preg_match('/^[012]$/', (string)$data['gender'])) throw new LogicException('用户性别参数错误');

    }

    /**
     * 注销用户
     * Author   Martinsun<syh@sunyonghong.com>
     * Date 2020-03-03
     *
     * @param Account $user
     * @return bool
     */
    public function deleteUser(Account $user): bool
    {
        $user->user_name   .= '_' . $user->id;
        $user->phone       .= '_' . $user->id;
        $user->delete_time = time();
        return $user->save();
    }

    /**
     * 用户登录
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-04
     */
    public function login(array $data)
    {
        // 验证数据
        $this->getValidate()->scene('logintype')->failException()->check($data);
        $this->getValidate()->scene('login' . $data['logintype'])->failException()->check($data);

        $phone = '';
        $map   = [
            'mhm_id' => request()->mhm_id
        ];

        if ($data['logintype'] == 'phone') {
            // 手机一键登录
            $phone        = app(Oneclick::class)->check($data['token'], $data['accessToken']);
            $map['phone'] = $phone;
        } else if ($data['logintype'] == 'user') {
            // 账号密码登录
            $map['phone|user_name'] = $data['user'];
        } else if ($data['logintype'] == 'verify') {
            // 手机验证码登录
            $map['phone'] = $phone = $data['phone'];
        }

        $user = $this->with(['avatar_url'])->where($map)->findOrEmpty();

        if ($user->isEmpty() && $data['logintype'] == 'user') {
            // 未找到账号且使用的密码登录
            throw new LogicException('账号或密码错误', '账号不存在');
        } else if ($user->isEmpty()) {
            // 未找到用户,自动注册
            $phone = $phone ?? $data['phone'];
            $this->addUser(['phone' => $phone]);
            $user                    = $this->with(['avatar_url'])->where($map)->findOrEmpty();
            $user->need_set_password = 1;
        } else if ($data['logintype'] == 'user') {
            // 有账号,且使用账号密码登录
            // 是否设置了密码
            if ($user->password == '') {
                throw new LogicException('账号或密码错误', '密码错误');
            }
            $inputPassword = $this->makePassword($data['password'], $user->salt);
            if (strtolower($inputPassword) != $user->getAttr('password')) {
                throw new LogicException('账号或密码错误', '密码错误');
            }
        } else if ($data['logintype'] == 'verify') {
            // 有账号,且使用验证码登录
            // 检查验证码
            $this->checkVerify($phone, $data['verify'], 'login');
        }

        // 账号状态是否正常
        return $this->setUserLoginInfo($user);
    }

    /**
     * 三方登录
     * Author 亓官雨树<lucky.max@foxmail.com>
     * Date：   2020-08-15
     */
    public function thirdlogin($param)
    {
        // 验证数据
        $this->getValidate()->scene('thirdlogin')->failException()->check($param);

        $user_id = app(ThirdLogin::class)->where($param)->value('user_id');
        if (!$user_id) throw new LogicException('用户未绑定');

        $user = $this->with(['avatar_url'])->findOrEmpty($user_id);
        if ($user->isEmpty()) throw new LogicException('账号不存在');

        // 账号状态是否正常
        return $this->setUserLoginInfo($user);
    }

    /**
     * 小程序微信登录
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/06/27
     *
     * @param $param
     * @return mixed
     * @throws GuzzleException
     */
    public function miniappLogin($param)
    {
        // 验证数据
        // $this->getValidate()->scene('miniapp')->failException()->check($param);
        $code   = $param['code'];
        $config = SystemConfig::get('system.login.miniapp', ['appid' => '', 'secret' => ''], request()->mhm_id);
        // 检测配置
        if (!$config['appid'] || !$config['secret']) throw new LogicException('配置错误，请联系后台人员');
        // access_token
        $uri      = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $config['appid'] . '&secret=' . $config['secret'];
        $client   = new Client();
        $res      = $client->request('GET', $uri);
        $response = $res->getBody()->getContents();
        $response = json_decode($response);
        if ($response->access_token) {
            $phoneUri = 'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=' . $response->access_token;
            $res      = $client->request('POST', $phoneUri, ['body' => json_encode(['code' => $code])]);
            $result   = $res->getBody()->getContents();
            $result   = json_decode($result);
            if ($result->errcode === 0) {
                $phone = $result->phone_info->purePhoneNumber;
                if (!$phone) throw new LogicException('获取手机号失败');
                // 非必要字段
                $hidden = Account::unnecessaryField();
                // 检测用户
                $user = app(Account::class)->with(['avatar_url'])->wherePhone($phone)->where('mhm_id', request()->mhm_id)->findOrEmpty();
                if ($user->isEmpty()) {
                    // 创建用户
                    $this->addUser(['phone' => $phone]);
                    $user                    = $this->with(['avatar_url'])->wherePhone($phone)->findOrEmpty();
                    $user->need_set_password = 1;
                } else {
                    $user->need_set_password = $user->password == '' ? 1 : 0;
                }
                // 活动券
                $user->ticket = $this->getUserLoginTicket($user->id, $user->last_login_time, $user->create_time);
                // 登录成功
                Event::subscribe(UserSubscribe::class);
                Event::trigger('UserLogin', $user);

                return $user->hidden($hidden);
            } else {
                throw new LogicException($result->errmsg);
            }
        } else {
            throw new LogicException($response->errmsg);
        }
    }

    // 苹果手机设备登录
    public function ioslogin($param)
    {
        // 验证数据
        $this->getValidate()->scene('ioslogin')->failException()->check($param);
        // 苹果手机设备号
        $code = $param['code'];
        // 存在登录号
        $user_id = app(ThirdLogin::class)->where('type', 'ios')->where('oauth', $code)->value('user_id');

        if ($user_id) {
            // 存在登录号，返回用户信息
            $user = $this->with(['avatar_url'])->findOrEmpty($user_id)->hidden($this::unnecessaryField());
            //
            $user->need_set_password = 0;
            // 如果没有设置密码,提示设置密码
            if ($user->password == '') $user->need_set_password = 1;
            // 活动券
            $user->ticket = $this->getUserLoginTicket($user->id, $user->last_login_time, $user->create_time);
            // 登录成功
            Event::subscribe(UserSubscribe::class);
            Event::trigger('UserLogin', $user);

            return $user;
        } else {
            // 不存在登录号，创建账号，返回用户信息
            return $this->createUserAndBind($code);
        }
    }

    // 苹果手机设备登录后绑定
    public function iosloginBind($param)
    {
        // 验证数据
        $this->getValidate()->scene('ioslogin_bind')->failException()->check($param);
        $user = request()->user;
        if (!$user) throw new LogicException('设备登录失效');

        $user->phone = $param['phone'];
        $user->save();
        // 登录成功
        Event::subscribe(UserSubscribe::class);
        Event::trigger('UserLogin', $user);

        return $user->hidden($this::unnecessaryField());
    }

    // 创建用户并绑定
    public function createUserAndBind($code, $mhmId = null)
    {
        $this->startTrans();
        try {
            $mhmId = $mhmId ?: request()->mhm_id;
            $user  = $this::create(['gender' => 0, 'mhm_id' => $mhmId]);

            if ($user->isEmpty()) throw new LogicException('登录失败', '设备登录创建用户失败');

            $user = $this->with(['avatar_url'])->findOrEmpty($user->id)->hidden($this::unnecessaryField());

            $user->need_set_password = 1;

            // 绑定
            $bind = ThirdLogin::create([
                'user_id' => $user->id,
                'type'    => 'ios',
                'oauth'   => $code
            ]);
            if ($bind->isEmpty()) throw new LogicException('登录失败', '设备登录绑定三方失败');

            // 注册成功
            // 注册积分
            $user->credit_key = 'register';
            $user->rel_pk     = $user->id;
            Event::subscribe(UserSubscribe::class);
            Event::trigger('UserReg', $user);

            $this->commit();

            return $user;
        } catch (LogicException $e) {
            $this->rollback();
            throw new LogicException($e->getMessage());
        }
    }

    /**
     * 检测账号状态
     * Author   Martinsun<syh@sunyonghong.com>
     * Date 2020-03-10
     *
     * @param Account $user [description]
     */
    public function checkStatus(Account $user): void
    {
        if ($user->status === 0) throw new LogicException('账号已被禁止登录', '账号被禁用');
    }

    /**
     * 设置密码
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-11
     */
    public function pwdSet(Account $user, array $data): Account
    {
        // 取得验证器
        $this->getValidate()->scene('pwdSet')->failException()->check($data);
        $user->password = $data['password'];
        if (!$user->save()) throw new LogicException('密码设置失败');

        return $user;
    }

    /**
     * 修改密码
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-11
     */
    public function pwdEdit(Account $user, array $data): Account
    {
        // 检查验证码
        // $this->checkVerify($data['phone'], $data['verify'], 'edit');
        // 检查原密码
        if ($user->getAttr('password') != $user->makePassword($data['ex_password'], $user->salt)) throw new LogicException('原密码错误');
        // 修改密码
        $user->password = $data['new_password'];
        if (!$user->save()) throw new LogicException('密码修改失败');

        $param = [
            'user' => $user->id,
            'type' => 'system_notify',
            'key'  => 'password_change',
            'var'  => [
                'user_name' => $user->nick_name,
            ],
        ];

        Event::subscribe(NotifyEvent::class);
        Event::trigger('Notify', $param);

        return $user;
    }

    /**
     * 忘记密码-手机验证
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-06
     */
    public function pwdVerify(array $data)
    {
        // 验证数据
        $this->getValidate()->scene('pwdVerify')->failException()->check($data);
        $map['phone']  = $phone = $data['phone'];
        $map['mhm_id'] = $data['mhm_id'] ?? request()->mhm_id;
        // 检查账户
        $user = $this->where($map)->findOrEmpty();
        if (!$user) throw new LogicException('账号或密码错误', '用户不存在');
        // 检查验证码
        $this->checkVerify($phone, $data['verify'], 'retrieve');
        cache($user['phone'] . '_pwdVerify', Str::random(16), 60);

        return cache($user['phone'] . '_pwdVerify');
    }

    /**
     * 忘记密码-重置密码
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-06
     *
     * @param array $data [description]
     */
    public function pwdReset(array $data)
    {
        // 验证数据
        $this->getValidate()->scene('pwdReset')->failException()->check($data);

        if ($data['pk'] != cache($data['phone'] . '_pwdVerify')) throw new LogicException('参数错误');

        $user = $this->with(['avatar_url'])
            ->where('phone', $data['phone'])
            ->where('mhm_id', $data['mhm_id'] ?? request()->mhm_id)
            ->findOrEmpty();

        if ($user->isEmpty()) throw new LogicException('系统错误', '用户不存在');

        $user->password = $data['password'];

        if (!$user->save()) throw new LogicException('修改失败');

        return $user;
    }

    /**
     * 忘记密码 - 同步（同页面）重置密码
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-03-16
     *
     * @param array $data [description]
     */
    public function pwdResetSync(array $data)
    {
        // 验证数据
        $this->getValidate()->scene('pwdResetSync')->failException()->check($data);
        // 检查当前账户
        $user = $this->with(['avatar_url'])
            ->where('phone', $data['phone'])
            ->where('mhm_id', $data['mhm_id'] ?? request()->mhm_id)
            ->findOrEmpty();
        if ($user->isEmpty()) throw new LogicException('系统错误', '用户不存在');
        // 检查验证码
        $this->checkVerify($data['phone'], $data['verify'], 'reset');

        $user->password = $data['password'];

        if (!$user->save()) throw new LogicException('修改失败');

        return $user;
    }

    /**
     * 检查验证码
     * Author   亓官雨树<lucky.max@foxmail.com>
     * Date 2020-04-20
     *
     * @param    [type]    $phone  [手机号]
     * @param    [type]    $verify [传入的验证码]
     * @param    [type]    $type   [验证类型【login、reset、retrieve、edit】]
     */
    protected function checkVerify($phone, $verify, $type)
    {
        if (!in_array($type, ['login', 'reset', 'retrieve', 'edit', 'phone'])) throw new LogicException('参数错误');

        app(Verify::class)->check($phone, $type, $verify);
    }

    public function verifyPhone($param)
    {
        $phone    = $param['phone'] ?? null;
        $verify   = $param['verify'] ?? null;
        $original = $param['original'] ?? null;

        if ($original) {
            // 原手机号
            // 检查验证码
            $this->checkVerify($phone, $verify, 'phone');
        } else {
            // 新手机号
            // 检查此手机号是否已绑定用户
            $exist = $this->where('phone', $phone)
                ->where('mhm_id', $param['mhm_id'] ?? request()->mhm_id)
                ->findOrEmpty();

            if (!$exist->isEmpty()) throw new LogicException('此手机号已绑定用户');

            // 检查验证码
            $this->checkVerify($phone, $verify, 'phone');

            // 缓存手机号，防止保存时篡改
            cache('verify_phone_' . request()->mid, $phone, 5 * 60);

        }

        // 返回当前手机号
        return $phone;
    }

    public function getUserNickName($user_id = 0)
    {
        $user_id = $user_id ?: request()->mid;
        $user    = $this->where('id', $user_id)->findOrEmpty();
        if ($user->isEmpty()) throw new LogicException('用户信息错误');

        return $user->nick_name;
    }

    public function getUserName($user_id = 0)
    {
        $user_id = $user_id ?: request()->mid;
        $user    = $this->where('id', $user_id)->findOrEmpty();
        if ($user->isEmpty()) throw new LogicException('用户信息错误');

        return $user->user_name;
    }

    /**
     * @throws ModelNotFoundException
     * @throws DbException
     * @throws DataNotFoundException
     */
    public function getUserInfo($user_id = 0)
    {
        $id   = $user_id ?: request()->mid;
        $user = $this->field('id,mhm_id,user_name,nick_name,rest_times,phone,gender,avatar,signature,superior_user_id,last_login_time,status')->with(['avatar_url'])->where('id', $id)->findOrEmpty();

        $user->is_vip = app(VipUser::class)->isVip();
        // 人脸认证状态
        /** @var Face $face */
        $face                = app(Face::class);
        $user->face_verified = $face->isVerified($id);

        return $user;
    }

    public function getUsermhmId($user_id = 0)
    {
        $id = $user_id ?: request()->mid;

        return $this->where('id', $id)->value('mhm_id');
    }

    public function getUserPhone($user_id)
    {
        $user = $this->findOrEmpty($user_id);

        return $user->isEmpty() ? '' : ($user->phone ?: '');
    }

    public function getSuperiors(int $user_id = 0): array
    {
        $user_id   = $user_id ?: request()->mid;
        $primary   = $this->whereId($user_id)->value('superior_user_id');
        $secondary = $primary ? $this->whereId($primary)->value('superior_user_id') : 0;

        return ['primary' => $primary, 'secondary' => $secondary];
    }

    /**
     * 用户登录活动券
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 23/07/17
     *
     * @param $uid
     * @param $lastLoginTime
     * @param $regTime
     * @return array
     */
    public function getUserLoginTicket($uid, $lastLoginTime, $regTime): array
    {
        $all          = app(Ticket::class)->target('0')->field('id')->buildSql();
        $new          = app(Ticket::class)->target('1')->field('id')->buildSql();
        $buy          = app(Ticket::class)->target('2')->field('id')->buildSql();
        $sil          = app(Ticket::class)->target('3')->field('id')->buildSql();
        $purchase     = app(PaymentOrder::class)->where('user_id', $uid)->whereIn('status', '20,30,40')->order('payment_time', 'DESC')->findOrEmpty();
        $purchaseTime = (!$purchase->isEmpty() && $purchase->payment_time) ? $purchase->payment_time : null;
        $raw          = [];
        try {
            // 新用户
            $raw[] = "(json_extract( `ext_data`, '$.start_time' ) < " . $regTime . " AND ticket_id IN $new)";
            // 购买用户
            $purchaseTime && $raw[] = '(' . $purchaseTime . " < (" . time() . " - json_extract( `ext_data`, '$.day' ) * 86400) AND ticket_id IN $buy)";
            // 全部用户
            $raw[] = "(ticket_id IN $all)";
            // 沉默用户
            $raw[] = '(' . $lastLoginTime . " < (json_extract( `ext_data`, '$.start_time' ) - json_extract( `ext_data`, '$.day' ) * 86400) AND ticket_id IN $sil)";
            // 条件组合
            $rawstr = implode(' OR ', $raw);
            // 已领取
            $module = app(CouponUser::class)->where('user_id', $uid)->whereNotNull('ticket_id')->field('ticket_id')->buildSql();
            // 查询
            $ticket = app(TicketCoupon::class)->field('id,coupon_type,maxprice,price,discount,effective_day')->whereRaw($rawstr)->where('derive_stime', '<', time())->where('derive_etime', '>', time())->whereRaw('id NOT IN' . $module)->select()->toArray();
            // halt($ticket);
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            throw new LogicException($e->getMessage());
        }

        return $ticket;
    }

    /**
     * setUserLoginInfo
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 23/07/17
     *
     * @param $user
     * @return mixed
     */
    protected function setUserLoginInfo($user)
    {
        $this->checkStatus($user);
        $user->need_set_password = 0;
        // 如果没有设置密码,提示设置密码
        if ($user->password == '') $user->need_set_password = 1;
        // 用户活动券
        $user->ticket = $this->getUserLoginTicket($user->id, $user->last_login_time, $user->create_time);
        // 登录成功
        Event::subscribe(UserSubscribe::class);
        Event::trigger('UserLogin', $user);

        return $user;
    }

}
